home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 51329 / 51329.xpi / chrome / firefound.jar / content / firefound.js < prev    next >
Text File  |  2009-12-14  |  26KB  |  863 lines

  1. /**
  2.  * The main namespace class for the FireFound service.
  3.  *
  4.  * @author chris
  5.  */
  6.  
  7. var FIREFOUND = {
  8.     /**
  9.      * Whether debug mode is turned on. Used mostly to control logging.
  10.      */
  11.     debug : true,
  12.     
  13.     /**
  14.      * Whether this instance of the service is active.
  15.      * This whole thing should really be replaced with a component that runs in the background,
  16.      * but I've run into some issues with that because of the broken geolocation support in Firefox.
  17.      * @see https://bugzilla.mozilla.org/show_bug.cgi?id=493615
  18.      */
  19.     active : false,
  20.     
  21.     /**
  22.      * Reference for this object's geolocation listener entry.
  23.      */
  24.     watchId : null,
  25.     
  26.     /**
  27.      * Flag to disable the pref observer in various instances.
  28.      */
  29.     loadingSettings : false,
  30.     
  31.     /**
  32.      * Hold the user's password in memory so that they're not prompted for the master password
  33.      * multiple times per browsing session if they're using it.
  34.      */
  35.     password : null,
  36.     
  37.     /**
  38.      * Holds the preferences service after initialization.
  39.      */
  40.     prefs : null,
  41.     
  42.     /**
  43.      * A timer we use to delay sending preferences to the server, since the observer is triggered
  44.      * after every keystroke in Fennec.
  45.      */
  46.     prefObserverTimeout : null,
  47.     
  48.     /**
  49.      * Stringbundle
  50.      */
  51.     strings : null,
  52.     
  53.     /**
  54.      * Returns the current FireFound username.
  55.      */
  56.     get account() { return this.prefs.getCharPref("username"); },
  57.     
  58.     /**
  59.      * Service initializer.
  60.      * 
  61.      * @param boolean bareBones Whether to load just the bare minimum to use this object.
  62.      */
  63.     load : function (bareBones) {
  64.         this.prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("extensions.firefound.");
  65.         this.prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
  66.         this.prefs.addObserver("", FIREFOUND, false);
  67.         
  68.         if (document.getElementById("firefound-bundle")) {
  69.             this.strings = document.getElementById("firefound-bundle");
  70.         }
  71.         
  72.         /**
  73.          * A bare-bones initialization would be for something like options dialog in Firefox
  74.          */
  75.         if (!bareBones) {
  76.             // Check if there's already a window running with the FireFound service.
  77.             var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator); 
  78.             var enumerator = wm.getEnumerator(null);  
  79.             
  80.             while (enumerator.hasMoreElements()) {
  81.                 var win = enumerator.getNext();
  82.                 
  83.                 if ((win != window) && (win.FIREFOUND && win.FIREFOUND.active)) {
  84.                     return;
  85.                 }
  86.             }
  87.             
  88.             this.active = true;
  89.             
  90.             // Register with the geolocation service.
  91.             this.watchId = GEOLOCATION.watchPosition(function (position) { FIREFOUND.newLocation(position); }, function (error) { FIREFOUND.locationError(error); });
  92.             
  93.             // If there's no account associated with this profile, prompt the user for one.
  94.             if (!this.account) {
  95.                 setTimeout(function () { FIREFOUND.getAccount(); }, 5000);
  96.             }
  97.             else {
  98.                 this.getPreferencesForFennec();
  99.             }
  100.         
  101.             // Show a first-run page if this is the first run after a new install or upgrade.
  102.             var version = Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager).getItemForID("firefound@efinke.com").version;
  103.             var oldVersion = this.prefs.getCharPref("version");
  104.         
  105.             if (version != oldVersion) {
  106.                 this.prefs.setCharPref("version", version);
  107.             
  108.                 setTimeout(function () {
  109.                     if (typeof Browser != 'undefined') {
  110.                         // Fennec
  111.                         Browser.addTab(FIREFOUND.prefs.getCharPref("host") + "/firstrun/", true);
  112.                     }
  113.                     else {
  114.                         // Firefox
  115.                         var browser = getBrowser();
  116.                         browser.selectedTab = browser.addTab(FIREFOUND.prefs.getCharPref("host") + "/firstrun/");
  117.                     }
  118.                 }, 3000);
  119.             }
  120.         }
  121.     },
  122.     
  123.     /**
  124.      * Cleans up loose ends.
  125.      */
  126.     unload : function () {
  127.         /**
  128.          * If this is an active instance, check for other windows and active one of them.
  129.          */
  130.         
  131.         this.prefs.removeObserver("", FIREFOUND);
  132.         
  133.         if (this.active) {
  134.             this.active = false;
  135.             
  136.             GEOLOCATION.clearWatch(this.watchId);
  137.             
  138.             /**
  139.              * Pass the torch to any other non-active FireFound-enabled window.
  140.              */
  141.             var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator); 
  142.             var enumerator = wm.getEnumerator(null);  
  143.             
  144.             while (enumerator.hasMoreElements()) {
  145.                 var win = enumerator.getNext();
  146.                 
  147.                 if (win != window && win.FIREFOUND && !win.FIREFOUND.active) {
  148.                     win.FIREFOUND.load();
  149.                     return;
  150.                 }
  151.             }
  152.         }
  153.     },
  154.     
  155.     /**
  156.      * Returns a reference to the active Firefound object, or false if none are found.
  157.      *
  158.      * @return boolean
  159.      */
  160.     
  161.     getActiveFireFound : function () {
  162.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator); 
  163.         var enumerator = wm.getEnumerator(null);  
  164.         
  165.         while (enumerator.hasMoreElements()) {
  166.             var win = enumerator.getNext();
  167.             
  168.             if (win.FIREFOUND && win.FIREFOUND.active) {
  169.                 return win.FIREFOUND;
  170.             }
  171.         }
  172.         
  173.         return false;
  174.     },
  175.     
  176.     /**
  177.      * Preference observer, used only for Fennec, but will probably be used for Firefox eventually anyway.
  178.      * We delay the actual observer code because it fires after every keystroke in Fennec.
  179.      * 
  180.      * @param string subject
  181.      * @param string topic The event being observed.
  182.      * @param string data In our case, the preference that was changed.
  183.      */
  184.     observe : function(subject, topic, data) {
  185.         clearTimeout(FIREFOUND.prefObserverTimeout);
  186.         FIREFOUND.prefObserverTimeout = setTimeout(function (subject, topic, data) { FIREFOUND.prefChange(subject, topic, data); }, 1000, subject, topic, data);
  187.     },
  188.     
  189.     /**
  190.      * Preference observer, used only for Fennec, but will probably be used for Firefox eventually anyway.
  191.      *
  192.      * @param string subject
  193.      * @param string topic The event being observed.
  194.      * @param string data In our case, the preference that was changed.
  195.      */
  196.     
  197.     prefChange : function (subject, topic, data) {
  198.         if (topic != "nsPref:changed") {
  199.             return;
  200.         }
  201.         
  202.         if (FIREFOUND.active && !FIREFOUND.loadingSettings) {
  203.             var preferences = null;
  204.             
  205.             switch(data) {
  206.                 case "fennec.emailAddress":
  207.                     var preferences = {
  208.                         "email" : this.prefs.getCharPref("fennec.emailAddress")
  209.                     };
  210.                 break;
  211.                 case "fennec.miles":
  212.                     var preferences = {
  213.                         "miles" : this.prefs.getIntPref("fennec.miles")
  214.                     };
  215.                 break;
  216.                 case "fennec.protection.history":
  217.                 case "fennec.protection.downloads":
  218.                 case "fennec.protection.formdata":
  219.                 case "fennec.protection.cache":
  220.                 case "fennec.protection.offlineApps":
  221.                 case "fennec.protection.passwords":
  222.                 case "fennec.protection.cookies":
  223.                 case "fennec.protection.sessions":
  224.                 case "fennec.protection.siteSettings":
  225.                     var preferences = {
  226.                         "data_protection" : {
  227.                             "history" : this.prefs.getBoolPref("fennec.protection.history"),
  228.                             "passwords" : this.prefs.getBoolPref("fennec.protection.passwords"),
  229.                             "downloads" : this.prefs.getBoolPref("fennec.protection.downloads"),
  230.                             "cookies" : this.prefs.getBoolPref("fennec.protection.cookies"),
  231.                             "formdata" : this.prefs.getBoolPref("fennec.protection.formdata"),
  232.                             "sessions" : this.prefs.getBoolPref("fennec.protection.sessions"),
  233.                             "cache" : this.prefs.getBoolPref("fennec.protection.cache"),
  234.                             "siteSettings" : this.prefs.getBoolPref("fennec.protection.siteSettings"),
  235.                             "offlineApps" : this.prefs.getBoolPref("fennec.protection.offlineApps")
  236.                         }
  237.                     };
  238.                 break;
  239.             }
  240.             
  241.             if (preferences) {
  242.                 /**
  243.                  * Update the setting on the server. 
  244.                  */
  245.                 this.sendPreferences(preferences);
  246.             }
  247.         }
  248.     },
  249.     
  250.     /**
  251.      * Checks if the environment is Fennec, and if so, retrieves the settings from the server.
  252.      */
  253.     
  254.     getPreferencesForFennec : function () {
  255.         var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
  256.         
  257.         if (appInfo.ID == '{a23983c0-fd0e-11dc-95ff-0800200c9a66}') {
  258.             // This is Fennec.
  259.             
  260.             function callback(rv) {
  261.                 FIREFOUND.loadingSettings = true;
  262.                 
  263.                 if (!rv.email) rv.email = "";
  264.                 
  265.                 FIREFOUND.prefs.setCharPref("fennec.emailAddress", rv.email);
  266.                 FIREFOUND.prefs.setIntPref("fennec.miles", rv.miles);
  267.                 FIREFOUND.prefs.setBoolPref("fennec.protection.history", rv.data_protection.history);
  268.                 FIREFOUND.prefs.setBoolPref("fennec.protection.passwords", rv.data_protection.passwords);
  269.                 FIREFOUND.prefs.setBoolPref("fennec.protection.downloads", rv.data_protection.downloads);
  270.                 FIREFOUND.prefs.setBoolPref("fennec.protection.cookies", rv.data_protection.cookies);
  271.                 FIREFOUND.prefs.setBoolPref("fennec.protection.formdata", rv.data_protection.formdata);
  272.                 FIREFOUND.prefs.setBoolPref("fennec.protection.sessions", rv.data_protection.sessions);
  273.                 FIREFOUND.prefs.setBoolPref("fennec.protection.cache", rv.data_protection.cache);
  274.                 FIREFOUND.prefs.setBoolPref("fennec.protection.siteSettings", rv.data_protection.siteSettings);
  275.                 FIREFOUND.prefs.setBoolPref("fennec.protection.offlineApps", rv.data_protection.offlineApps);
  276.                 
  277.                 FIREFOUND.loadingSettings = false;
  278.             }
  279.             
  280.             this.getPreferences(callback);
  281.         }
  282.     },
  283.     
  284.     /**
  285.      * Initialization for the settings dialog in Firefox.
  286.      */
  287.     getPreferencesForFirefox : function () {
  288.         // Code to load the preference data for the full-blown settings window in Firefox.
  289.         
  290.         function callback(rv) {
  291.             document.getElementById("loading").style.visibility = "hidden";
  292.             document.getElementById("controls").style.visibility = "visible";
  293.             
  294.             if ("msg" in rv) {
  295.                 if ("code" in rv && rv.code == "ERROR_NO_ACCOUNT") {
  296.                     FIREFOUND.getActiveFireFound().getAccount();
  297.                     window.close();
  298.                 }
  299.                 else {
  300.                     alert(rv.msg);
  301.                 }
  302.             }
  303.             else {
  304.                 var email = rv.email
  305.                 var miles = rv.miles;
  306.                 var edp = rv.data_protection;
  307.                 
  308.                 document.getElementById("email").value = email;
  309.                 document.getElementById("miles").value = miles;
  310.                 
  311.                 document.getElementById("edp_history").checked = edp.history;
  312.                 document.getElementById("edp_passwords").checked = edp.passwords;
  313.                 document.getElementById("edp_downloads").checked = edp.downloads;
  314.                 document.getElementById("edp_cookies").checked = edp.cookies;
  315.                 document.getElementById("edp_formdata").checked = edp.formdata;
  316.                 document.getElementById("edp_sessions").checked = edp.sessions;
  317.                 document.getElementById("edp_cache").checked = edp.cache;
  318.                 document.getElementById("edp_siteSettings").checked = edp.siteSettings;
  319.                 document.getElementById("edp_offlineApps").checked = edp.offlineApps;
  320.             }
  321.         }
  322.         
  323.         this.getPreferences(callback);
  324.     },
  325.     
  326.     /**
  327.      * Prompt the user to choose a username and password (or supply an existing pair).
  328.      * 
  329.      * @param function successCallback The callback to call when all is said and done with this function.
  330.      * @param function errorCallback The error callback.
  331.      */
  332.     getAccount : function (successCallback, errorCallback) {
  333.         this.password = null;
  334.         
  335.         var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  336.                                 .getService(Components.interfaces.nsIPromptService);
  337.         username = { value: FIREFOUND.account };
  338.         password = { value: "" };
  339.         check = { value: false };
  340.         okorcancel = prompts.promptUsernameAndPassword(window, this.strings.getString("firefound.registration.title"), this.strings.getString("firefound.registration.description"), username, password, null, check);
  341.         
  342.         if (!okorcancel) {
  343.             if (errorCallback) {
  344.                 errorCallback();
  345.             }
  346.             else {
  347.                 // Don't bug the user for the rest of this window's session.
  348.                 this.unload();
  349.             }
  350.         }
  351.         else {
  352.             // Post the auth pair to the FireFound service.
  353.             var json = {
  354.                 username : username.value,
  355.                 password : password.value
  356.             };
  357.         
  358.             json = JSON.stringify(json);
  359.         
  360.             var req = new XMLHttpRequest();
  361.             req.parent = this;
  362.             req.open("POST", this.prefs.getCharPref("host") + "/api/account.json", true);
  363.         
  364.             req.setRequestHeader("Content-Type", "application/json");
  365.             req.setRequestHeader("Content-Length", json.length);
  366.         
  367.             req.onreadystatechange = function () {
  368.                 if (req.readyState == 4) {
  369.                     if (FIREFOUND.prefs.getBoolPref("debug")) {
  370.                         FIREFOUND.log("getAccount: " + req.responseText);
  371.                     }
  372.                     
  373.                     if (req.status == 200) {
  374.                         // Success: Save this username and password.
  375.                         req.parent.prefs.setCharPref("username", username.value.toLowerCase());
  376.                         req.parent.setPassword(username.value.toLowerCase(), password.value);
  377.                         
  378.                         // Request the current location as a baseline.
  379.                         if (req.parent.watchId) {
  380.                             GEOLOCATION.getCurrentPosition(
  381.                                 function (position) { 
  382.                                     FIREFOUND.newLocation(position); 
  383.                                 },
  384.                                 function (error) {
  385.                                     FIREFOUND.locationError(error);
  386.                                 }
  387.                             );
  388.                         }
  389.                         
  390.                         if (successCallback) {
  391.                             successCallback();
  392.                         }
  393.                         
  394.                         /**
  395.                          * In case this is Fennec, download the current settings.
  396.                          */
  397.                         FIREFOUND.getPreferencesForFennec();
  398.                     }
  399.                     else {
  400.                         // Could be an invalid username or incorrect password.
  401.                         var rv = JSON.parse(req.responseText);
  402.                         
  403.                         alert(rv.msg);
  404.                         
  405.                         // Try again.
  406.                         req.parent.getAccount(successCallback, errorCallback);
  407.                     }
  408.                 }
  409.             };
  410.         
  411.             req.send(json);
  412.         }
  413.     },
  414.     
  415.     /**
  416.      * Retrieves user preferences from the server and populates the settings form.
  417.      * 
  418.      * @param function callback The function that is called with the preferences retrieved from the server.
  419.      */
  420.     
  421.     getPreferences : function (callback) {
  422.         var username = this.account;
  423.         
  424.         if (!this.account) {
  425.             this.getAccount(function () { FIREFOUND.getPreferences(); }, function () { window.close(); });
  426.             return;
  427.         }
  428.         
  429.         var password = this.getPassword(username);
  430.         
  431.         if (!password) {
  432.             this.getAccount(function () { FIREFOUND.getPreferences(); }, function () { window.close(); });
  433.             return;
  434.         }
  435.         
  436.         var json = {
  437.             username : username,
  438.             password : password
  439.         };
  440.     
  441.         json = JSON.stringify(json);
  442.     
  443.         var req = new XMLHttpRequest();
  444.         req.parent = this;
  445.         req.open("POST", this.prefs.getCharPref("host") + "/api/preferences.json", true);
  446.     
  447.         req.setRequestHeader("Content-Type", "application/json");
  448.         req.setRequestHeader("Content-Length", json.length);
  449.     
  450.         req.onreadystatechange = function () {
  451.             if (req.readyState == 4) {
  452.                 if (callback) {
  453.                     var rv = JSON.parse(req.responseText);
  454.                     
  455.                     if (callback) {
  456.                         callback(rv);
  457.                     }
  458.                 }
  459.             }
  460.         };
  461.     
  462.         req.send(json);
  463.     },
  464.     
  465.     /**
  466.      * Saves user preference changes to the server (called from settings.xul)
  467.      *
  468.      * @return boolean Returns false to prevent the dialog from closing before the request completes.
  469.      */
  470.     
  471.     setPreferences : function () {
  472.         document.getElementById("loading").selectedIndex = 1;
  473.         document.getElementById("loading").style.visibility = "visible";
  474.         
  475.         var preferences = {
  476.             "email" : document.getElementById("email").value,
  477.             "miles" : document.getElementById("miles").value,
  478.             "data_protection" : {
  479.                 "history" : document.getElementById("edp_history").checked,
  480.                 "passwords" : document.getElementById("edp_passwords").checked,
  481.                 "downloads" : document.getElementById("edp_downloads").checked,
  482.                 "cookies" :document.getElementById("edp_cookies").checked,
  483.                 "formdata" :document.getElementById("edp_formdata").checked,
  484.                 "sessions" :document.getElementById("edp_sessions").checked,
  485.                 "cache" :document.getElementById("edp_cache").checked,
  486.                 "siteSettings" :document.getElementById("edp_siteSettings").checked,
  487.                 "offlineApps" :document.getElementById("edp_offlineApps").checked,
  488.             }
  489.         };
  490.     
  491.         function callback () { window.close(); };
  492.         
  493.         this.sendPreferences(preferences, callback);
  494.         
  495.         return false;
  496.     },
  497.     
  498.     /**
  499.      * Updates the settings on the FireFound server.
  500.      * 
  501.      * @param dict preferences The preferences to send to the server.
  502.      * @param function callback The callback after the request completes.
  503.      */
  504.     
  505.     sendPreferences : function (preferences, callback) {
  506.         // Both of these were set when the dialog loaded.
  507.         var username = this.account;
  508.         var password = this.getPassword(username);
  509.         
  510.         var json = {
  511.             username : username,
  512.             password : password,
  513.             preferences : preferences
  514.         };
  515.         
  516.         json = JSON.stringify(json);
  517.     
  518.         var req = new XMLHttpRequest();
  519.         req.parent = this;
  520.         req.open("POST", this.prefs.getCharPref("host") + "/api/preferences.json", true);
  521.         
  522.         req.setRequestHeader("Content-Type", "application/json");
  523.         req.setRequestHeader("Content-Length", json.length);
  524.     
  525.         req.onreadystatechange = function () {
  526.             if (req.readyState == 4) {
  527.                 if (callback) {
  528.                     callback();
  529.                 }
  530.             }
  531.         };
  532.     
  533.         req.send(json);
  534.     },
  535.     
  536.     /**
  537.      * Saves the chosen username/password pair to the login manager.
  538.      *
  539.      * @param string The chosen username.
  540.      * @param string The chosen password.
  541.      */
  542.     
  543.     setPassword : function (username, password) {
  544.         this.password = password;
  545.         
  546.         // Everything done with the username is lower-cased.
  547.         username = username.toLowerCase();
  548.         
  549.         // TODO Centralize this code.
  550.         var loginManager = Components.classes["@mozilla.org/login-manager;1"].getService(Components.interfaces.nsILoginManager);
  551.         var url = this.prefs.getCharPref("host") + "/";
  552.         
  553.         var logins = loginManager.findLogins({}, url, "chrome://firefound", null);
  554.         
  555.         for (var j = 0; j < logins.length; j++) {
  556.             loginManager.removeLogin(logins[j]);
  557.         }
  558.         
  559.         var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo, "init");
  560.         var loginInfo = new nsLoginInfo(url, 'chrome://firefound', null, username, password, "", "");
  561.         loginManager.addLogin(loginInfo);
  562.     },
  563.     
  564.     /**
  565.      * Retrieve the FireFound password for the given username.
  566.      *
  567.      * @param string The username.
  568.      * @return string/bool The password, or false on failure.
  569.      */
  570.     
  571.     getPassword : function (username) {
  572.         if (this.password) {
  573.             return this.password;
  574.         }
  575.         
  576.         var loginManager = Components.classes["@mozilla.org/login-manager;1"].getService(Components.interfaces.nsILoginManager);
  577.         var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo, "init");
  578.         
  579.         var hostname = this.prefs.getCharPref("host") + "/";
  580.         var formSubmitURL = "chrome://firefound";
  581.         
  582.         var logins = loginManager.findLogins({}, hostname, formSubmitURL, null);
  583.         
  584.         for (var i = 0; i < logins.length; i++) {
  585.             this.password = logins[i].password;
  586.             return logins[i].password;
  587.         }
  588.         
  589.         return false;
  590.     },
  591.     
  592.     /**
  593.      * The new location callback for the geolocation service.
  594.      *
  595.      * @param hash The new location data.
  596.      */
  597.     
  598.     newLocation : function (location) {
  599.         var username = this.account;
  600.         
  601.         if (!this.account) {
  602.             return;
  603.         }
  604.         
  605.         var password = this.getPassword(username);
  606.         
  607.         if (!password) {
  608.             this.getAccount();
  609.             return;
  610.         }
  611.         
  612.         // Ping it out to the FireFound server.
  613.         
  614.         var json = {
  615.             "username" : username,
  616.             "password" : password,
  617.             "location" : location
  618.         };
  619.         
  620.         json.location = FIREFOUND_CRYPT.encrypt(JSON.stringify(location), password);
  621.         json.encrypted = true;
  622.         
  623.         json = JSON.stringify(json);
  624.         
  625.         var req = new XMLHttpRequest();
  626.         req.parent = this;
  627.         
  628.         req.open("POST", this.prefs.getCharPref("host") + "/api/location.json", true);
  629.         
  630.         req.setRequestHeader("Content-Type", "application/json");
  631.         req.setRequestHeader("Content-Length", json.length);
  632.         
  633.         req.onreadystatechange = function () {
  634.             if (req.readyState == 4) {
  635.                 if (FIREFOUND.prefs.getBoolPref("debug")) {
  636.                     FIREFOUND.log("newLocation: " + req.responseText);
  637.                 }
  638.                 
  639.                 if (req.status == 200) {
  640.                     if (req.responseText) {
  641.                         // The only thing right now that would be in the response is a killswitch activation.
  642.                         var rv = JSON.parse(req.responseText);
  643.                         
  644.                         if (rv.killswitch) {
  645.                             // Get a password.
  646.                             req.parent.threaten(rv.killswitch_fields);
  647.                         }
  648.                     }
  649.                 }
  650.                 else {
  651.                     // Some error occurred.
  652.                     var rv = JSON.parse(req.responseText);
  653.                     
  654.                     if ("code" in rv && rv.code == "ERROR_NO_ACCOUNT") {
  655.                         // Disable the add-on.  The user has either closed their account,
  656.                         // or they haven't been active in 60 days.
  657.                         Components.classes["@mozilla.org/extensions/manager;1"]
  658.                             .getService(Components.interfaces.nsIExtensionManager)
  659.                             .disableItem("firefound@efinke.com");
  660.                         
  661.                         FIREFOUND.prefs.setCharPref("username", "");
  662.                         
  663.                         FIREFOUND.getActiveFireFound().unload();
  664.                     }
  665.                 }
  666.             }
  667.         };
  668.         
  669.         req.send(json);
  670.     },
  671.     
  672.     /**
  673.      * Threaten to clear all data if the password is not entered.
  674.      * 
  675.      * @param array[string] fields The types of data that will be cleared on failure.
  676.      */
  677.     
  678.     threaten : function (fields) {
  679.         var re = [];
  680.         
  681.         // The code in threat.xul takes care of closing after 30 seconds.
  682.         window.openDialog("chrome://firefound/content/threat.xul", "threat", "chrome,modal,centerscreen,resizable=no", re);
  683.         
  684.         // Confirm that the password is correct.
  685.         var username = re[0].toLowerCase();
  686.         var password = re[1];
  687.         
  688.         if (username != this.account || password != this.getPassword(username)) {
  689.             this.abortAbort(fields);
  690.         }
  691.         else {
  692.             // Ping the FireFound server and clear the killswitch.
  693.             var json = {
  694.                 "username" : username,
  695.                 "password" : password,
  696.                 "preferences" : {
  697.                     "killswitch": false
  698.                 }
  699.             };
  700.  
  701.             json = JSON.stringify(json);
  702.  
  703.             var req = new XMLHttpRequest();
  704.             req.parent = this;
  705.  
  706.             req.open("POST", this.prefs.getCharPref("host") + "/api/preferences.json", true);
  707.             req.setRequestHeader("Content-Type", "application/json");
  708.             req.setRequestHeader("Content-Length", json.length);
  709.  
  710.             req.onreadystatechange = function () {
  711.                 if (req.readyState == 4) {
  712.                     if (FIREFOUND.prefs.getBoolPref("debug")) {
  713.                         req.parent.log("Threaten: " + req.responseText);
  714.                     }
  715.                 }
  716.             };
  717.  
  718.             req.send(json);
  719.         }
  720.     },
  721.     
  722.     /**
  723.      * Clear out all private data.
  724.      * 
  725.      * @param array[string] fields The types of data that will be cleared.
  726.      */
  727.     
  728.     abortAbort : function (fields) {
  729.         // Save the username/password so that FireFound can continue to act as a locater beacon.
  730.         var username = this.account;
  731.         var password = this.getPassword(username);
  732.         
  733.         var json = {
  734.             "username" : username,
  735.             "password" : password,
  736.             "activated": true
  737.         };
  738.         
  739.         json = JSON.stringify(json);
  740.         
  741.         var san = new Sanitizer();
  742.         
  743.         for (var i = 0; i < fields.length; i++) {
  744.             var field = fields[i];
  745.             
  746.             san.clearItem(field);
  747.             
  748.             if (field == "passwords") {
  749.                 // Reset the FireFound username/password so we can continue to track location.
  750.                 this.setPassword(username, password);
  751.             }
  752.         }
  753.         
  754.         var req = new XMLHttpRequest();
  755.         req.parent = this;
  756.         
  757.         req.open("POST", this.prefs.getCharPref("host") + "/api/killswitch.json", true);
  758.         req.setRequestHeader("Content-Type", "application/json");
  759.         req.setRequestHeader("Content-Length", json.length);
  760.         
  761.         req.onreadystatechange = function () {
  762.             if (req.readyState == 4) {
  763.                 // Shut down.
  764.                 var appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1'].getService(Components.interfaces.nsIAppStartup);
  765.                 appStartup.quit(Components.interfaces.nsIAppStartup.eForceQuit);
  766.             }
  767.         };
  768.         
  769.         req.send(json);
  770.     },
  771.     
  772.     /**
  773.      * Retrieves and saves a KML file of the user's locations.
  774.      */
  775.     
  776.     downloadLocations : function () {
  777.         if (document.getElementById("loading")) {
  778.             // Show the "Retrieving data" loader.
  779.             document.getElementById("loading").selectedIndex = 2;
  780.             document.getElementById("loading").style.visibility = 'visible';
  781.         }
  782.         
  783.         // Save the username/password so that FireFound can continue to act as a locater beacon.
  784.         var username = this.account;
  785.         var password = this.getPassword(username);
  786.         
  787.         var json = {
  788.             "username" : username,
  789.             "password" : password,
  790.         };
  791.         
  792.         json = JSON.stringify(json);
  793.         
  794.         var req = new XMLHttpRequest();
  795.         req.parent = this;
  796.         
  797.         req.open("POST", this.prefs.getCharPref("host") + "/api/download.json", true);
  798.         req.setRequestHeader("Content-Type", "application/json");
  799.         req.setRequestHeader("Content-Length", json.length);
  800.         
  801.         req.onreadystatechange = function () {
  802.             if (req.readyState == 4) {
  803.                 // Prompt the user to save it.
  804.                 var data = req.responseText;
  805.                 
  806.                 var nsIFilePicker = Components.interfaces.nsIFilePicker;
  807.                 var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  808.                 fp.init(window, req.parent.strings.getString("firefound.download.title"), nsIFilePicker.modeSave);
  809.  
  810.                 fp.appendFilter(req.parent.strings.getString("firefound.download.kml"), "*.kml");
  811.                 fp.appendFilter(req.parent.strings.getString("firefound.download.all"), "*");
  812.                 
  813.                 fp.defaultString = req.parent.strings.getString("firefound.download.filename") + ".kml";
  814.                 
  815.                 var result = fp.show();
  816.  
  817.                 if (result != nsIFilePicker.returnCancel){
  818.                     var file = fp.file;
  819.                     
  820.                     var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].getService(Components.interfaces.nsIScriptableUnicodeConverter);
  821.                     converter.charset = 'UTF-8';
  822.                     data = converter.ConvertFromUnicode(data);
  823.  
  824.                     var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
  825.  
  826.                     outputStream.init(file, 0x04 | 0x08 | 0x20, 420, 0 );
  827.                     outputStream.write(data, data.length);
  828.                     outputStream.close();
  829.                 }
  830.                 
  831.                 if (document.getElementById("loading")) {
  832.                     document.getElementById("loading").style.visibility = 'hidden';
  833.                 }
  834.             }
  835.         };
  836.         
  837.         req.send(json);
  838.     },
  839.     
  840.     /**
  841.      * Error callback for geolocation service.
  842.      *
  843.      * @param string The error string.
  844.      */
  845.     
  846.     locationError : function (error) {
  847.         if (FIREFOUND.prefs.getBoolPref("debug")) {
  848.             this.log("locationError: " + error);
  849.         }
  850.     },
  851.     
  852.     /**
  853.      * Log a message to the Error Console.
  854.      *
  855.      * @param string message
  856.      */
  857.     
  858.     log : function (message) {
  859.         var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
  860.         consoleService.logStringMessage("FIREFOUND: " + message);
  861.     }
  862. };
  863.